use openssl::asn1::Asn1Time;
use openssl::bn::{BigNum, MsbOption};
use openssl::error::ErrorStack;
use openssl::hash::MessageDigest;
use openssl::nid::Nid;
use openssl::pkey::{PKey, Private};
use openssl::x509::extension::{
    AuthorityKeyIdentifier, BasicConstraints, ExtendedKeyUsage, KeyUsage, SubjectAlternativeName,
    SubjectKeyIdentifier,
};
use openssl::x509::{X509, X509NameBuilder, X509Ref, X509Req, X509ReqBuilder};

pub fn gen_ca_openssl() -> (PKey<Private>, X509) {
    // 生成密钥对
    let rsa_key = openssl::rsa::Rsa::generate(4096).unwrap();
    let key_pair = PKey::from_rsa(rsa_key).unwrap();

    // 配置x509相关参数
    let mut x509_name = X509NameBuilder::new().unwrap();
    x509_name
        .append_entry_by_nid(Nid::COUNTRYNAME, "CN")
        .unwrap();
    x509_name
        .append_entry_by_nid(Nid::STATEORPROVINCENAME, "Henan")
        .unwrap();
    x509_name
        .append_entry_by_nid(Nid::LOCALITYNAME, "Zhengzhou")
        .unwrap();
    x509_name
        .append_entry_by_nid(Nid::ORGANIZATIONNAME, "liming's CA")
        .unwrap();
    x509_name
        .append_entry_by_nid(Nid::ORGANIZATIONALUNITNAME, "liming's CA")
        .unwrap();
    x509_name
        .append_entry_by_nid(Nid::COMMONNAME, "ca")
        .unwrap();
    x509_name
        .append_entry_by_text("emailAddress", "1802717840@qq.com")
        .unwrap();
    let x509_name = x509_name.build();

    let mut cert_builder = openssl::x509::X509::builder().unwrap();
    cert_builder.set_version(2).unwrap();
    let serial_number = {
        let mut serial = BigNum::new().unwrap();
        serial.rand(159, MsbOption::MAYBE_ZERO, false).unwrap();
        serial.to_asn1_integer().unwrap()
    };
    cert_builder.set_serial_number(&serial_number).unwrap();
    cert_builder.set_subject_name(&x509_name).unwrap();
    cert_builder.set_issuer_name(&x509_name).unwrap();
    cert_builder.set_pubkey(&key_pair).unwrap();
    // 设置过期时间
    let not_before = Asn1Time::days_from_now(0).unwrap();
    let not_after = Asn1Time::days_from_now(3650).unwrap();
    cert_builder.set_not_before(&not_before).unwrap();
    cert_builder.set_not_after(&not_after).unwrap();

    cert_builder
        .append_extension(BasicConstraints::new().ca().pathlen(3).build().unwrap())
        .unwrap();
    cert_builder
        .append_extension(
            KeyUsage::new()
                .key_cert_sign()
                .crl_sign()
                .digital_signature()
                .key_encipherment()
                .data_encipherment()
                .build()
                .unwrap(),
        )
        .unwrap();
    cert_builder
        .append_extension(
            ExtendedKeyUsage::new()
                .client_auth()
                .server_auth()
                .build()
                .unwrap(),
        )
        .unwrap();

    let subject_key_identifier = SubjectKeyIdentifier::new()
        .build(&cert_builder.x509v3_context(None, None))
        .unwrap();

    cert_builder
        .append_extension(subject_key_identifier)
        .unwrap();
    cert_builder
        .sign(&key_pair, MessageDigest::sha256())
        .unwrap();
    let cert = cert_builder.build();
    (key_pair, cert)
}

/// 根据密钥对，生成证书请求
fn gen_req_openssl(key_pair: &PKey<Private>) -> Result<X509Req, ErrorStack> {
    let mut req_builder = X509ReqBuilder::new()?;
    req_builder.set_pubkey(&key_pair)?;

    let mut x509_name = X509NameBuilder::new()?;
    x509_name.append_entry_by_nid(Nid::COUNTRYNAME, "CN")?;
    x509_name.append_entry_by_nid(Nid::STATEORPROVINCENAME, "Henan")?;
    x509_name.append_entry_by_nid(Nid::LOCALITYNAME, "Zhengzhou")?;
    x509_name.append_entry_by_nid(Nid::ORGANIZATIONNAME, "liming's WebSit")?;
    x509_name.append_entry_by_nid(Nid::ORGANIZATIONALUNITNAME, "liming's WebSit")?;
    x509_name.append_entry_by_nid(Nid::COMMONNAME, "www.li-ming.top")?;
    x509_name.append_entry_by_text("emailAddress", "1802717840@qq.com")?;
    let x509_name = x509_name.build();
    req_builder.set_subject_name(&x509_name)?;
    req_builder.sign(&key_pair, MessageDigest::sha256())?;
    let req = req_builder.build();
    Ok(req)
}

/// 签名证书请求，生成证书
pub fn sign_cert_openssl(
    ca_pair: &PKey<Private>, // ca证书的密钥对
    ca_cert: &X509Ref,       // ca证书
) -> Result<(X509, PKey<Private>), ErrorStack> {
    let rsa = openssl::rsa::Rsa::generate(4096)?;
    let key_pair = PKey::from_rsa(rsa)?;
    let req = gen_req_openssl(&key_pair)?;

    let mut x509_builder = X509::builder()?;
    x509_builder.set_version(2)?;
    let serial_number = {
        let mut serial = BigNum::new()?;
        serial.rand(159, MsbOption::MAYBE_ZERO, false)?;
        serial.to_asn1_integer()?
    };
    x509_builder.set_serial_number(&serial_number)?;
    x509_builder.set_subject_name(&req.subject_name())?;
    x509_builder.set_issuer_name(ca_cert.subject_name())?;
    x509_builder.set_pubkey(&key_pair)?;
    let not_before = Asn1Time::days_from_now(0)?;
    let not_after = Asn1Time::days_from_now(3650)?;
    x509_builder.set_not_before(&not_before)?;
    x509_builder.set_not_after(&not_after)?;
    x509_builder.append_extension(BasicConstraints::new().build().unwrap())?;
    x509_builder.append_extension(
        KeyUsage::new()
            .key_encipherment()
            .data_encipherment()
            .build()?,
    )?;
    x509_builder.append_extension(
        ExtendedKeyUsage::new()
            .client_auth()
            .server_auth()
            .build()?,
    )?;
    let subject_key_identifier =
        SubjectKeyIdentifier::new().build(&x509_builder.x509v3_context(Some(ca_cert), None))?;
    let authority_key_identifier = AuthorityKeyIdentifier::new()
        .keyid(false)
        .issuer(false)
        .build(&x509_builder.x509v3_context(Some(ca_cert), None))?;
    x509_builder.append_extension(subject_key_identifier)?;
    x509_builder.append_extension(authority_key_identifier)?;
    let san = SubjectAlternativeName::new()
        .dns("www.li-ming.top")
        .dns("localhost")
        .ip("127.0.0.1")
        .build(&x509_builder.x509v3_context(Some(ca_cert), None))?;
    x509_builder.append_extension(san)?;

    x509_builder.sign(&ca_pair, MessageDigest::sha256())?;
    let cert = x509_builder.build();
    Ok((cert, key_pair))
}

#[test]
fn test_all() {
    let (ca_key, ca_cert) = gen_ca_openssl();
    let (cert, key) = sign_cert_openssl(&ca_key, &ca_cert).unwrap();
    let (cert1, key1) = sign_cert_openssl(&ca_key, &ca_cert).unwrap();
    println!(
        "ca key : \n{}",
        String::from_utf8(ca_key.private_key_to_pem_pkcs8().unwrap()).unwrap()
    );
    println!(
        "ca cert: \n{}",
        String::from_utf8(ca_cert.to_pem().unwrap()).unwrap()
    );

    println!(
        "cert key : \n{}",
        String::from_utf8(key.private_key_to_pem_pkcs8().unwrap()).unwrap()
    );
    println!(
        "cert     : \n{}",
        String::from_utf8(cert.to_pem().unwrap()).unwrap()
    );
    println!(
        "cert key : \n{}",
        String::from_utf8(key1.private_key_to_pem_pkcs8().unwrap()).unwrap()
    );
    println!(
        "cert     : \n{}",
        String::from_utf8(cert1.to_pem().unwrap()).unwrap()
    );
}
